﻿using System;
using System.Collections.Generic;
using System.Linq;
using Business.Logic;
using Newtonsoft.Json;
using System.Xml.Serialization;

namespace Business.Entities
{
    /// <summary>
    ///  The list of tasks
    /// </summary>
    [Serializable]
    public class TaskList 
    {
        /// <summary>
        /// When no category is chosen in Outlook, this is used in google for the list name
        /// </summary>
        [JsonIgnore]
        public const string DefaultListName = "Default";

        #region Json Properties

        /// <summary>
        /// The tasks
        /// </summary>
        public List<Task> child_entity;

        /// <summary>
        /// Last modified (millisec since 1970)
        /// </summary>
        public long last_modified;

        /// <summary>
        /// Google type
        /// </summary>
        public string type;

        /// <summary>
        /// Google id
        /// </summary>
        public string id;

        /// <summary>
        /// The name
        /// </summary>
        public string name;

        /// <summary>
        /// Metadata    
        /// </summary>
        [XmlIgnore]
        public Dictionary<string, long> list_metadata;
        
        #endregion

        #region Other properties
        /// <summary>
        /// Returns the list name or "Default" for the default list name (empty || null)
        /// </summary>
        [JsonIgnore]
        [XmlIgnore]
        public string NameSafe
        {
            get 
            {
                if (String.IsNullOrEmpty(name)) return DefaultListName;
                return name;
            }
        }

        [XmlIgnore]
        [JsonIgnore]
        public TasksRoot Root;

        /// <summary>
        /// Timestamp for last modified. Mirrors last_modified
        /// </summary>
        [JsonIgnore]
        [XmlIgnore]
        public DateTime LastModified
        {
            get
            {
                // Parse _lastmodified
                return StringUtil.GooglTimeToDateTime(last_modified);
            }
            set
            {
                last_modified = StringUtil.DateTimeToGoogleTime(value);
            }
        }
        
        /// <summary>
        /// True if this task has a Google ID (i.e. not temporary guid)
        /// </summary>
        [JsonIgnore]
        [XmlIgnore]
        public bool HasGoogleId
        {
            get { return id != null && id.Length != Task.LengthOfGuid; }
        }

        /// <summary>
        /// Returns google id. Null if temp id.
        /// </summary>
        [XmlIgnore]
        [JsonIgnore]
        public string GoogleId
        {
            get
            {
                if (!HasGoogleId) return null;
                return id;
            }
        }
        #endregion

        #region Constructors
        public TaskList()
        {
            child_entity = new List<Task>();
            id = Guid.NewGuid().ToString(); // Temp ID before stored to google
        }

        /// <summary>
        /// Constructor setting default name
        /// </summary>
        /// <param name="name">Name</param>
        public TaskList(string name) : this()
        {
            this.name = name;
        }
        #endregion

        #region Business methods
        /// <summary>
        /// Adds an element to this list
        /// </summary>
        /// <param name="task"></param>
        public void Add(Task task) 
        {
            task.ParentList = this;
            task.list_id = new string[1];
            task.list_id[0] = id;
            child_entity.Add(task);
        }

        /// <summary>
        /// Returns the list sorted by ordinal
        /// </summary>
        /// <returns></returns>
        public TaskList SortedByOrdinal()
        {
            var sortLinq = child_entity.OrderBy(p => p.Ordinal);
            var sorted = new TaskList();
            sorted.name = name;
            sorted.id = id;
            sorted.last_modified = last_modified;
            sorted.Root = Root;
            foreach (var elem in sortLinq) sorted.Add(elem);
            //var dic = new SortedDictionary<int, Task>();
            //foreach (var elem in child_entity) dic.Add(elem.Ordinal, elem);
            //foreach (var elem in dic)
            //{
            //    sorted.child_entity.Add(elem.Value);
            //}
            return sorted;
        }

        public override string ToString()
        {
            int count = child_entity == null ? 0 : child_entity.Count;
            return name + " (" + count + ")";
        }

        /// <summary>
        /// Adds or replaces existing task with matching id if any
        /// </summary>
        /// <param name="task"></param>
        internal void Replace(Task task)
        {
            bool found = false;
            for (int i = 0; i < child_entity.Count; i++)
            {
                var existing = child_entity[i];
                if (existing.id == task.id)
                {
                    child_entity[i] = task;
                    found = true;
                    break;
                }
            }
            if (!found) child_entity.Add(task);
            task.ParentList = this;

            // Makre sure the id is our id
            if (!String.IsNullOrEmpty(id))
            {
                task.list_id = new string[1];
                task.list_id[0] = id;
            }
        }

        /// <summary>
        /// Returns the highest index number
        /// </summary>
        /// <returns></returns>
        public int GetMaxIndex()
        {
            //if (child_entity == null) return 0;
            //var maxIndex = 0;
            //foreach (var elem in child_entity)
            //{
            //    if (elem.Ordinal > maxIndex) maxIndex = elem.Ordinal;
            //}
            //return maxIndex;
            return child_entity.Count-2;
        }

        /// <summary>
        /// Returns task with specified id or null if not found
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public Task GetTask(string id)
        {
            foreach (Task task in child_entity)
            {
                if (task.id == id) return task;
            }
            return null;
        }

        public override bool Equals(object obj)
        {
            var tObj = obj as TaskList;
            if (tObj == null) return base.Equals(obj);
            return tObj.id.Equals(id);
        }

        internal void Remove(Task gTask)
        {
            child_entity.Remove(gTask);
        }

        public int GetMaxOrdinal()
        {
            int maxOrdinal = Int32.MinValue;
            foreach (Task task in child_entity)
            {
                if (task.Ordinal > maxOrdinal) maxOrdinal = task.Ordinal;
            }
            if (maxOrdinal == Int32.MinValue) return 0;
            return maxOrdinal;
        }

        /// <summary>
        /// Sets ordinal indentation, parent and siblings and ordinal
        /// </summary>
        public void PostProcess()
        {
            //Task prevTask = null;
            foreach (Task task in child_entity)
            {
                if (task.child_id != null)
                {
                    // Set parent
                    foreach (string childId in task.child_id)
                    {
                        var child = GetTask(childId);
                        if (child != null)
                        {
                            task.Children.Add(child);
                            child.ParentTask = task;
                            ///child.IndentLevel = task.IndentLevel + 1;
                        }
                    }
                }
                // Set prior sibling
                //if (prevTask != null && prevTask.IndentLevel == task.IndentLevel) task.PriorTask = prevTask;
                //task.PriorTask = GetPriorSibling(task);
                //prevTask = task;
            }
            // Set ordinal
            SetOrdinal(child_entity, 1);
            foreach (Task task in child_entity)
            {
                if (task.ParentTask != null) continue;
                SetIndentRec(task, 0);
            }

        }

        private void SetIndentRec(Task task, int ind)
        {
            task.IndentLevel = ind;
            foreach (Task child in task.Children) SetIndentRec(child, ind + 1);

        }

        /// <summary>
        /// Depth first traversal of task tree to set ordinals
        /// </summary>
        /// <param name="list"></param>
        /// <param name="ordinal"></param>
        /// <returns></returns>
        private int SetOrdinal(List<Task> list, int ordinal)
        {
            foreach (Task task in list)
            {
                if(task.Ordinal == 0) task.Ordinal = ordinal++;
                ordinal = SetOrdinal(task.Children, ordinal);
            }
            return ordinal;
        }


        public List<Task> GetSiblingsSorted(Task task)
        {
            var list = SortedByOrdinal().child_entity;
            var newList = new List<Task>();
            foreach (Task iTask in list)
            {
                if (iTask.ParentTask == task.ParentTask) newList.Add(iTask);
            }
            return newList;
        }


        public Task GetPriorSibling(Task task)
        {
            Task prior = null;
            foreach (Task iTask in GetSiblingsSorted(task))
            {
                if (iTask == task) return prior;
                prior = iTask;
            }
            return null;
        }

        public Task GetNextSibling(Task task)
        {
            bool now = false;
            foreach (Task iTask in GetSiblingsSorted(task))
            {
                if (now) return iTask;
                if (iTask == task) now = true;
            }
            return null;
        }

        /// <summary>
        /// Changes the parent task/list of the task to new id
        /// </summary>
        /// <param name="task">Task who's parent to change</param>
        /// <param name="newParentId">ID of new parent. If not in list (or list id specified), null is set as ParentTask</param>
        public void SetTaskParent(Task task, string newParentId)
        {
            if (task.ParentTask != null)
            {
                task.ParentTask.RemoveChild(task);
            }
            if (newParentId != null)
            {
                Task newParent = GetTask(newParentId);
                //if (newParent == null) throw new ArgumentException("Could not find new parent " + newParentTaskId + " in SetTaskParent()");
                if (newParent != null) newParent.AddChild(task);
                task.ParentTask = newParent;
            }
            else task.ParentTask = null;
        }

        public void DeleteTask(Task task)
        {
            // Move all children to parent
            Task[] childCopy = new Task[task.Children.Count];
            task.Children.CopyTo(childCopy);
            foreach (Task child in childCopy)
            {
                SetTaskParent(child, task.ParentElementId);
                child.IndentLevel--;
            }
            child_entity.Remove(task);
        }

        /// <summary>
        /// Replaces the temporaryId with the newId for ChildIds of all tasks
        /// </summary>
        public void ReplaceChildIds(string prevId, string newId)
        {
            foreach (Task task in child_entity)
            {
                if (task.child_id != null)
                {
                    for (int i = 0; i < task.child_id.Length; i++)
                    {
                        if (task.child_id[i] == prevId) task.child_id[i] = newId;
                    }
                }
            }
        }
        /// <summary>
        /// Sets a new id to this list and copies it down to the tasks
        /// </summary>
        /// <param name="newId">new id</param>
        public void SetNewId(string newId)
        {
            id = newId;
            foreach (Task task in child_entity)
            {
                task.ListId = newId; 
            }
        }

        /// <summary>
        /// String compare where "" == "Default"
        /// </summary>
        /// <param name="one"></param>
        /// <param name="two"></param>
        /// <returns></returns>
        public static bool ListNameEquals(string one, string two)
        {

            if (one == String.Empty && two == null) return true;
            if (one == null && two == string.Empty) return true;
            if (one == DefaultListName && two == null) return true;
            if (one == DefaultListName && two == String.Empty) return true;
            if (one == null && two == DefaultListName) return true;
            if (one == String.Empty && two == DefaultListName) return true;
            return String.Compare(one, two, true) == 0;
        }
        #endregion
        
    }
}
